<template>
    <div class="th-filter" :class="{ 'is-filtered': !!hasValue }">
      <span v-if="label">{{ label }}</span>
  
      <el-select
        v-model="innerValue"
        :filterable="true"
        :remote="true"
        :clearable="clearable"
        :disabled="disabled"
        :placeholder="placeholder"
        :remote-method="onRemote"
        :loading="loading"
        :multiple="multiple"
        :collapse-tags="collapseTags"
        :collapse-tags-tooltip="collapseTagsTooltip"
        :allow-create="allowCreate"
        :default-first-option="defaultFirstOption"
        :reserve-keyword="true"
        :size="size"
        v-bind="$attrs"
        style="width: 100%"
        @visible-change="onVisible"
        @change="emitChange"
        @clear="emitClear"
        @remove-tag="onRemoveTag"
      >
        <!-- 自定义 option 渲染插槽（可选） -->
        <template v-if="$slots.option" v-for="opt in options" :key="getKey(opt)">
          <el-option :label="getLabel(opt)" :value="packValue(opt)">
            <slot name="option" :option="opt" />
          </el-option>
        </template>
  
        <!-- 默认 option 渲染 -->
        <template v-else>
          <el-option
            v-for="opt in options"
            :key="getKey(opt)"
            :label="getLabel(opt)"
            :value="packValue(opt)"
          />
        </template>
  
        <!-- 空态：智能提示或错误 -->
        <template #empty>
          <div class="th-empty">
            <template v-if="errorText">
              <div class="th-empty-title">加载失败</div>
              <div class="th-empty-desc">{{ errorText }}</div>
              <el-button link @click="reload()">重试</el-button>
            </template>
            <template v-else-if="minCharsHint">
              <div class="th-empty-desc">{{ minCharsHint }}</div>
            </template>
            <template v-else>
              <div class="th-empty-desc">{{ noDataTextComputed }}</div>
            </template>
          </div>
        </template>
  
        <!-- 透传 prefix/suffix 插槽（可选） -->
        <template v-if="$slots.prefix" #prefix>
          <slot name="prefix" />
        </template>
        <template v-if="$slots.suffix" #suffix>
          <slot name="suffix" />
        </template>
      </el-select>
    </div>
  </template>
  
  <script setup>
  import { ref, watch, computed, onMounted } from 'vue'
  
  /**
   * 最全 props
   */
  const props = defineProps({
    /** v-model */
    modelValue: { type: [String, Number, Object, Array, null], default: null },
  
    /** 左侧小标题 */
    label: { type: String, default: '' },
  
    /** 占位 */
    placeholder: { type: String, default: '下拉选择 / 输入回车搜索' },
  
    /**
     * 远程取数函数（必填）
     *  (query: string) => Promise<Array<any>>
     *  你可以返回任意结构的对象，下面用 labelKey/valueKey 映射
     */
    fetchOptions: { type: Function, required: true },
  
    /** 选项字段映射 */
    labelKey: { type: String, default: 'label' },
    valueKey: { type: String, default: 'value' },
    keyKey:   { type: String, default: 'value' }, // key 优先用这个字段
  
    /** 是否回传整条 option（true）还是仅回传其 value（false） */
    returnWhole: { type: Boolean, default: true },
  
    /** 输入多少字符后才触发搜索（空串用于首屏预取） */
    minChars: { type: Number, default: 0 },
  
    /** 展开下拉时，若本地还没有数据，是否预取一次（query=''） */
    prefetchOnOpen: { type: Boolean, default: true },
  
    /** 去抖毫秒 */
    debounce: { type: Number, default: 300 },
  
    /** 缓存开关、容量与 TTL（毫秒）*/
    cache: { type: Boolean, default: true },
    cacheSize: { type: Number, default: 50 },
    cacheTTL: { type: Number, default: 5 * 60 * 1000 }, // 5 分钟
  
    /** 空态文案（留空会自动根据 minChars 提示） */
    noDataText: { type: String, default: '' },
  
    /** 下拉行为细节 */
    allowCreate: { type: Boolean, default: true },
    defaultFirstOption: { type: Boolean, default: true },
  
    /** 选择形态 */
    multiple: { type: Boolean, default: false },
    collapseTags: { type: Boolean, default: true },
    collapseTagsTooltip: { type: Boolean, default: true },
  
    /** 交互细节 */
    clearable: { type: Boolean, default: true },
    disabled: { type: Boolean, default: false },
    size: { type: String, default: 'small' }, // 'default' | 'large' | 'small'
  })
  
  const emit = defineEmits([
    'update:modelValue',
    'change',
    'clear',
    'visible-change',
    'search',
    'fetch-success',
    'fetch-error',
    'remove-tag'
  ])
  
  /** 内部状态 */
  const innerValue = ref(props.modelValue)
  const options = ref([])
  const loading = ref(false)
  const errorText = ref('')
  const currentQuery = ref('')
  
  /** 同步 v-model */
  watch(() => props.modelValue, v => (innerValue.value = v))
  watch(innerValue, v => emit('update:modelValue', v))
  
  /** 有值？（用于顶端文字高亮） */
  const hasValue = computed(() => {
    if (props.multiple) return Array.isArray(innerValue.value) && innerValue.value.length > 0
    return innerValue.value !== null && innerValue.value !== undefined && innerValue.value !== ''
  })
  
  /** 智能空态提示 */
  const minCharsHint = computed(() => {
    const q = currentQuery.value || ''
    if (q && q.length < props.minChars) {
      return `请输入至少 ${props.minChars} 个字符开始搜索`
    }
    return ''
  })
  const noDataTextComputed = computed(() => props.noDataText || '无匹配数据')
  
  /** 字段访问工具 */
  const getLabel = (opt) => opt?.[props.labelKey]
  const getValue = (opt) => opt?.[props.valueKey]
  const getKey   = (opt) => opt?.[props.keyKey] ?? getValue(opt)
  const packValue = (opt) => (props.returnWhole ? opt : getValue(opt))
  
  /** LRU + 过期（TTL）缓存 */
  const lru = {
    map: new Map(), // key -> {ts, data}
    touch(key) { const v = this.map.get(key); if (!v) return; this.map.delete(key); this.map.set(key, v) },
    set(key, data) {
      const now = Date.now()
      this.map.set(key, { ts: now, data })
      // 超容量淘汰最老
      while (this.map.size > props.cacheSize) {
        const first = this.map.keys().next().value
        this.map.delete(first)
      }
    },
    get(key) {
      const v = this.map.get(key)
      if (!v) return null
      const expired = (Date.now() - v.ts) > props.cacheTTL
      if (expired) { this.map.delete(key); return null }
      this.touch(key)
      return v.data
    },
    clear() { this.map.clear() }
  }
  
  /** 去抖执行器 */
  let timer = null
  let seq = 0 // 竞态序号，保证只落地最后一次
  const debounced = (fn, ms) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(fn, ms)
  }
  
  /** 核心取数 */
  const doFetch = async (query) => {
    currentQuery.value = query
    errorText.value = ''
    const q = (query || '').trim()
  
    if (q && q.length < props.minChars) {
      options.value = []
      return
    }
  
    // 缓存命中
    const cacheKey = q.toLowerCase()
    if (props.cache) {
      const cached = lru.get(cacheKey)
      if (cached) { options.value = cached; return }
    }
  
    const my = ++seq
    loading.value = true
    try {
      const list = await props.fetchOptions(q)
      if (my !== seq) return // 被新请求覆盖
      const arr = Array.isArray(list) ? list : []
      options.value = arr
      emit('fetch-success', { query: q, count: arr.length })
      if (props.cache) lru.set(cacheKey, arr)
    } catch (e) {
      if (my !== seq) return
      const msg = (e && (e.message || e.msg)) || '请求失败'
      errorText.value = msg
      emit('fetch-error', { query: q, error: e })
      options.value = []
    } finally {
      if (my === seq) loading.value = false
    }
  }
  
  /** 远程方法（供 el-select 调用） */
  const onRemote = (q) => {
    emit('search', q)
    debounced(() => doFetch(q), props.debounce)
  }
  
  /** 展开事件：首开预取 */
  const onVisible = (v) => {
    emit('visible-change', v)
    if (v && props.prefetchOnOpen && options.value.length === 0) {
      doFetch('')
    }
  }
  
  /** change/clear/remove-tag */
  const emitChange = (v) => emit('change', v)
  const emitClear  = () => emit('clear')
  const onRemoveTag = (v) => emit('remove-tag', v)
  
  /** 对外暴露的方法 */
  const reload = () => doFetch(currentQuery.value || '')
  const reset  = () => { innerValue.value = props.multiple ? [] : null }
  const setOptions = (arr) => { options.value = Array.isArray(arr) ? arr : [] ; errorText.value='' }
  const focus  = () => nextTick(() => {
    const el = (document.querySelector('.th-filter .el-select') || {}).__vueParentComponent?.exposed
    el && el.focus && el.focus()
  })
  defineExpose({ reload, reset, setOptions, focus, clearCache: () => lru.clear() })
  
  /** 可选：组件挂载后就预取一次（很多场景体验更丝滑） */
  onMounted(() => {
    if (props.prefetchOnOpen) doFetch('')
  })
  </script>
  
  <style scoped>
  .th-filter {
    display: grid;
    gap: 6px;
    align-items: center;
    justify-items: start;
    padding: 6px 6px 8px;
    width: 100%;
  }
  .th-filter > span {
    font-size: 12px;
    color: #606266;
    line-height: 1;
  }
  .th-filter.is-filtered > span {
    color: #3e8cf0;
    font-weight: 600;
  }
  .th-filter :deep(.el-input__wrapper),
  .th-filter :deep(.el-select .el-input__wrapper) {
    box-shadow: none;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    padding: 0 8px;
    min-height: 28px;
  }
  .th-filter :deep(.is-focus) { border-color: #3e8cf0; }
  
  .th-empty { padding: 10px 8px; text-align: center; }
  .th-empty-title { font-weight: 600; margin-bottom: 4px; }
  .th-empty-desc { font-size: 12px; color: #909399; }
  </style>
  